#include <stdio.h>  // snprintf
#include<functional>
#include<assert.h>
#include"Logger.h"
#include"Connector.h"
#include"TcpClient.h"
using namespace wjc;
using namespace std;
using namespace Log;

TcpClient::TcpClient(const InetAddress& serverAddr, const std::string& name)
    : mainCore_(new Core(1,true,name)),
    connector_(new Connector(mainCore_.get(), serverAddr)),
    name_(name),
    retry_(false),
    connect_(true),
    nextConnId_(1)
{
    connector_->setNewConnectionCallback(
        std::bind(&TcpClient::newConnection, this, std::placeholders::_1));
    mainCore_->start();
    // FIXME setConnectFailedCallback
    LOG_INFO << "TcpClient::TcpClient[" << name_
        << "] - connector " /*<< get_pointer(connector_)*/;
}

TcpClient::TcpClient(std::shared_ptr<Core> core, const InetAddress& serverAddr,const string& name)
    : mainCore_(core),
    connector_(new Connector(core.get(), serverAddr)),
    name_(name),
    retry_(false),
    connect_(true),
    nextConnId_(1)
{
    connector_->setNewConnectionCallback(
        std::bind(&TcpClient::newConnection, this, std::placeholders::_1));
    mainCore_->start();
    // FIXME setConnectFailedCallback
    LOG_INFO << "TcpClient::TcpClient[" << name_
        << "] - connector " /*<< get_pointer(connector_)*/;
}

TcpClient::~TcpClient()
{
    LOG_INFO << "TcpClient::~TcpClient[" << name_
        << "] - connector " /*<< get_pointer(connector_)*/;
    ConnectionPtr conn;
    bool unique = false;
    {
        std::lock_guard<std::mutex> lock(mutex_);
        unique = connection_.unique();
        conn = connection_;
    }
    if (conn)
    {
        //assert(mainCore_.get() == conn->getCore());
        // FIXME: not 100% safe, if we are in different thread
        //mainCore_->runInCore(CreateTask(std::bind(&Connection::connectDestroyed, conn)));
        //if (unique)
        //{
            conn->close();
        //}
    }
    else
    {
        connector_->stop();
        // FIXME: HACK
    }
}

void TcpClient::connect()
{
    // FIXME: check state
    LOG_INFO << "TcpClient::connect[" << name_ << "] - connecting to "
        << connector_->serverAddress().toIpPort();
    connect_ = true;
    connector_->start();
}

void TcpClient::disconnect()
{
    connect_ = false;

    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (connection_)
        {
            connection_->shutdown();
        }
    }
}

void TcpClient::stop()
{
    connect_ = false;
    connector_->stop();
    mainCore_->quit();
}

bool TcpClient::send(const std::string& msg) {
    bool flag = false;
    //???
    std::lock_guard<std::mutex> lock(mutex_);
    if (connection_&& connection_->getState() == Connection::kConnected) {
        flag = true;
        connection_->send(msg);
    }
    return flag;
}

bool TcpClient::sendMsg(MessagePtr& msg) {
    bool flag = false;
    //???
    std::lock_guard<std::mutex> lock(mutex_);
    if (connection_ && connection_->getState() == Connection::kConnected) {
        flag = true;
        connection_->sendMsg(msg);
    }
    return flag;
}

void TcpClient::newConnection(int sockfd)
{
    if (!mainCore_->isInCoreThread())
        mainCore_->runInCore(CreateTask(std::bind(&TcpClient::newConnection, this, sockfd)));
    InetAddress peerAddr(sockets::getPeerAddr(sockfd));
    char buf[32];
    snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toIpPort().c_str(), nextConnId_);
    ++nextConnId_;
    string connName = name_ + buf;

    InetAddress localAddr(sockets::getLocalAddr(sockfd));
    // FIXME poll with zero timeout to double confirm the new connection
    // FIXME use make_shared if necessary
    ConnectionPtr conn(new Connection(mainCore_.get(),connName,sockfd,localAddr,peerAddr));

    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);
    conn->setCloseCallback(
        std::bind(&TcpClient::removeConnection, this, std::placeholders::_1)); // FIXME: unsafe
    {
        std::lock_guard<std::mutex> lock(mutex_);
        connection_ = conn;
    }
    conn->connectEstablished();
}

void TcpClient::removeConnection(const ConnectionPtr& conn)
{
    assert(mainCore_.get() == conn->getCore());

    {
        std::lock_guard<std::mutex> lock(mutex_);
        assert(connection_ == conn);
        connection_.reset();
    }

    mainCore_->runInCore(CreateTask(std::bind(&Connection::connectDestroyed, conn)));
    if (retry_ && connect_)
    {
        LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to "
            << connector_->serverAddress().toIpPort();
        connector_->restart();
    }
}
